{
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  },
  "name": ""
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "### Card and Deck objects\n",
      "\n",
      "This notebook contains example code from [*Fluent Python*](http://shop.oreilly.com/product/0636920032519.do), by Luciano Ramalho.\n",
      "\n",
      "Code by Luciano Ramalho, modified by Allen Downey.\n",
      "\n",
      "MIT License: https://opensource.org/licenses/MIT"
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "This example demonstrates the Python data model using a simple implementation of playing cards and decks.\n",
      "\n",
      "`Card` is a namedtuple that represents a playing card."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": true,
     "input": [
      "import collections\n",
      "\n",
      "Card = collections.namedtuple('Card', ['rank', 'suit'])"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "`FrenchDeck` is a class that represents a deck of cards."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": true,
     "input": [
      "class FrenchDeck:\n",
      "    ranks = [str(n) for n in range(2, 11)] + list('JQKA')\n",
      "    suits = 'spades diamonds clubs hearts'.split()\n",
      "\n",
      "    def __init__(self):\n",
      "        self._cards = [Card(rank, suit) for suit in self.suits\n",
      "                                        for rank in self.ranks]\n",
      "\n",
      "    def __len__(self):\n",
      "        return len(self._cards)\n",
      "\n",
      "    def __getitem__(self, position):\n",
      "        return self._cards[position]"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "You can instantiate a `Card` object as if `Card` were a class.\n",
      "\n",
      "BTW: [beer card](https://en.wikipedia.org/wiki/Beer_card)"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "beer_card = Card('7', 'diamonds')\n",
      "beer_card"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "You can access the fields of a card by name."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "beer_card.rank, beer_card.suit"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Or by index."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "beer_card[0], beer_card[1]"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "A drawback of using namedtuples is that you can't define methods for them in the usual way.\n",
      "\n",
      "But you can [monkey-patch](https://en.wikipedia.org/wiki/Monkey_patch) them by defining a function and then making it an attribute of `Card`.  For example, here's a function that generates a string representation of a card:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "def card_to_str(card):\n",
      "    return '%s of %s' % card\n",
      "\n",
      "card_to_str(beer_card)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Here's how we can make that function behave like a method.  When we pass a card to `print`, Python invokes the special method `__str__`"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "Card.__str__ = card_to_str\n",
      "print(beer_card)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Now let's instantiate a `FrenchDeck`.\n",
      "\n",
      "When we call `len`, Python invokes the `__len__` method on the deck. "
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "deck = FrenchDeck()\n",
      "len(deck)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "When we use the bracket operator, Python invokes the `__getitem__` method:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "deck[3]"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "And that means that the slice operator works, too:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "deck[:3]"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Aside: In this context, we don't get the string generated by `__str__`; we get the one generated by `__repr__` (read about that [here](https://docs.python.org/3/reference/datamodel.html#basic-customization)) \n",
      "\n",
      "Because `FrenchDeck` provides `__len__` and `__getitem__`, it is considered a sequence, which means that the `in` operator works:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "Card('Q', 'hearts') in deck"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "**Exercise** Make up a card that doesn't exist and confirm that `in` returns `False`."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Solution goes here"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "And the for loop works, too:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "for card in deck:\n",
      "    print(card)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Other methods that work with sequences, like `random.choice`, will work with decks:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from random import choice\n",
      "choice(deck)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Sadly, `shuffle` doesn't work because we haven't provided `__setitem__`, so a deck is an immutable sequence:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from random import shuffle\n",
      "\n",
      "# This should raise a TypeError\n",
      "shuffle(deck)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "We can use `sorted` to iterate through the cards in the order determined by tuple comparison:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "for card in sorted(deck):\n",
      "    print(card)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "If we want an ordering that makes more sense for cards, we can define a function that maps from a card to an integer:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)\n",
      "\n",
      "def spades_high_ordering(card):\n",
      "    rank_value = FrenchDeck.ranks.index(card.rank)\n",
      "    return rank_value * len(suit_values) + suit_values[card.suit]"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "spades_high_ordering(Card('2', 'clubs'))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "spades_high_ordering(Card('A', 'spades'))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "And then pass this funcition as a key to `sorted`:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "for card in sorted(deck, key=spades_high_ordering):\n",
      "    print(card)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "**Exercise**  Define a new ordering that sorts the cards by suit first and then by rank, so all clubs come first, followed by all diamonds, etc."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Solution goes here"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "**Exercise**  Write a method called `setcard` that takes a deck, an index, and a card, and assigns the card to the deck at the given position.  Then monkey-patch `FrenchDeck` to provide `__setitem__` as a method.  Test it by assigning a new card like this:\n",
      "\n",
      "    deck[0] = Card('A', 'spades')\n",
      "\n",
      "Then shuffle the deck using `random.shuffle`."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Solution goes here"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "We should have two Aces of spades now, which we can confirm by checking the number of unique cards:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "len(set(deck))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    },
    {
     "cell_type": "code",
     "collapsed": true,
     "input": [],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": null
    }
   ],
   "metadata": {}
  }
 ]
}
